ISSDK  1.8
IoT Sensing Software Development Kit
fusion.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015, Freescale Semiconductor, Inc.
3  * Copyright 2016-2017 NXP
4  * All rights reserved.
5  *
6  * SPDX-License-Identifier: BSD-3-Clause
7  */
8 
9 // This is the file that contains the fusion routines. It is STRONGLY RECOMMENDED
10 // that the casual developer NOT TOUCH THIS FILE.
11 
12 /*! \file fusion.c
13  \brief Lower level sensor fusion interface
14 */
15 
16 #include "stdio.h"
17 #include "math.h"
18 #include "stdlib.h"
19 
20 #include "sensor_fusion.h"
21 #include "fusion.h"
22 #include "orientation.h"
23 #include "matrix.h"
24 #include "approximations.h"
25 #include "drivers.h"
26 #include "control.h"
27 
28 //////////////////////////////////////////////////////////////////////////////////////////////////
29 // intialization functions for the sensor fusion algorithms
30 //////////////////////////////////////////////////////////////////////////////////////////////////
31 
32 // function initializes the sensor fusion and magnetic calibration and sets loopcounter to zero
34 {
35  // reset the quaternion type to the default packet type
36  // sfg->pControlSubsystem->QuaternionPacketType = sfg->pControlSubsystem->DefaultQuaternionPacketType;
37 
38  // force a reset of all the algorithms next time they execute
39  // the initialization will result in the default and current quaternion being set to the most sophisticated
40  // algorithm supported by the build
41 #if F_1DOF_P_BASIC
42  sfg->SV_1DOF_P_BASIC.resetflag = true;
43 #endif
44 #if F_3DOF_B_BASIC
45  sfg->SV_3DOF_B_BASIC.resetflag = true;
46 #endif
47 #if F_3DOF_G_BASIC
48  sfg->SV_3DOF_G_BASIC.resetflag = true;
49 #endif
50 #if F_3DOF_Y_BASIC
51  sfg->SV_3DOF_Y_BASIC.resetflag = true;
52 #endif
53 #if F_6DOF_GB_BASIC
54  sfg->SV_6DOF_GB_BASIC.resetflag = true;
55 #endif
56 #if F_6DOF_GY_KALMAN
57  sfg->SV_6DOF_GY_KALMAN.resetflag = true;
58 #endif
59 #if F_9DOF_GBY_KALMAN
60  sfg->SV_9DOF_GBY_KALMAN.resetflag = true;
61 #endif
62 
63  // reset the loop counter to zero for first iteration
64  sfg->loopcounter = 0;
65  return;
66 }
67 
68 void fFuseSensors(struct SV_1DOF_P_BASIC *pthisSV_1DOF_P_BASIC,
69  struct SV_3DOF_G_BASIC *pthisSV_3DOF_G_BASIC,
70  struct SV_3DOF_B_BASIC *pthisSV_3DOF_B_BASIC,
71  struct SV_3DOF_Y_BASIC *pthisSV_3DOF_Y_BASIC,
72  struct SV_6DOF_GB_BASIC *pthisSV_6DOF_GB_BASIC,
73  struct SV_6DOF_GY_KALMAN *pthisSV_6DOF_GY_KALMAN,
74  struct SV_9DOF_GBY_KALMAN *pthisSV_9DOF_GBY_KALMAN,
75  struct AccelSensor *pthisAccel,
76  struct MagSensor *pthisMag,
77  struct GyroSensor *pthisGyro,
78  struct PressureSensor *pthisPressure,
79  struct MagCalibration *pthisMagCal)
80 {
81  // 1DOF Pressure: call the low pass filter algorithm
82 #if F_1DOF_P_BASIC
83  if (pthisSV_1DOF_P_BASIC)
84  {
85  ARM_systick_start_ticks(&(pthisSV_1DOF_P_BASIC->systick));
86  fRun_1DOF_P_BASIC(pthisSV_1DOF_P_BASIC, pthisPressure);
87  pthisSV_1DOF_P_BASIC->systick = ARM_systick_elapsed_ticks(pthisSV_1DOF_P_BASIC->systick);
88  }
89 #endif
90 
91  // 3DOF Accel Basic: call the tilt algorithm
92 #if F_3DOF_G_BASIC
93  if (pthisSV_3DOF_G_BASIC)
94  {
95  ARM_systick_start_ticks(&(pthisSV_3DOF_G_BASIC->systick));
96  fRun_3DOF_G_BASIC(pthisSV_3DOF_G_BASIC, pthisAccel);
97  pthisSV_3DOF_G_BASIC->systick = ARM_systick_elapsed_ticks(pthisSV_3DOF_G_BASIC->systick);
98  }
99 #endif
100 
101  // 3DOF Magnetometer Basic: call the 2D vehicle compass algorithm
102 #if F_3DOF_B_BASIC
103  if (pthisSV_3DOF_B_BASIC)
104  {
105  ARM_systick_start_ticks(&(pthisSV_3DOF_B_BASIC->systick));
106  fRun_3DOF_B_BASIC(pthisSV_3DOF_B_BASIC, pthisMag);
107  pthisSV_3DOF_B_BASIC->systick = ARM_systick_elapsed_ticks(pthisSV_3DOF_B_BASIC->systick);
108  }
109 #endif
110 
111  // 3DOF Gyro Basic: call the gyro integration algorithm
112 #if F_3DOF_Y_BASIC
113  if (pthisSV_3DOF_Y_BASIC)
114  {
115  ARM_systick_start_ticks(&(pthisSV_3DOF_Y_BASIC->systick));
116  fRun_3DOF_Y_BASIC(pthisSV_3DOF_Y_BASIC, pthisGyro);
117  pthisSV_3DOF_Y_BASIC->systick = ARM_systick_elapsed_ticks(pthisSV_3DOF_Y_BASIC->systick);
118  }
119 #endif
120 
121  // 6DOF Accel / Mag: Basic: call the eCompass orientation algorithm
122 #if F_6DOF_GB_BASIC
123  if (pthisSV_6DOF_GB_BASIC)
124  {
125  ARM_systick_start_ticks(&(pthisSV_6DOF_GB_BASIC->systick));
126  fRun_6DOF_GB_BASIC(pthisSV_6DOF_GB_BASIC, pthisMag, pthisAccel);
127  pthisSV_6DOF_GB_BASIC->systick = ARM_systick_elapsed_ticks(pthisSV_6DOF_GB_BASIC->systick);
128  }
129 #endif
130 
131  // 6DOF Accel / Gyro: call the Kalman filter orientation algorithm
132 #if F_6DOF_GY_KALMAN
133  if (pthisSV_6DOF_GY_KALMAN)
134  {
135  ARM_systick_start_ticks(&(pthisSV_6DOF_GY_KALMAN->systick));
136  fRun_6DOF_GY_KALMAN(pthisSV_6DOF_GY_KALMAN, pthisAccel, pthisGyro);
137  pthisSV_6DOF_GY_KALMAN->systick = ARM_systick_elapsed_ticks(pthisSV_6DOF_GY_KALMAN->systick);
138  }
139 #endif
140 
141  // 9DOF Accel / Mag / Gyro: call the Kalman filter orientation algorithm
142 #if F_9DOF_GBY_KALMAN
143  if (pthisSV_9DOF_GBY_KALMAN)
144  {
145  ARM_systick_start_ticks(&(pthisSV_9DOF_GBY_KALMAN->systick));
146  fRun_9DOF_GBY_KALMAN(pthisSV_9DOF_GBY_KALMAN, pthisAccel, pthisMag,
147  pthisGyro, pthisMagCal);
148  pthisSV_9DOF_GBY_KALMAN->systick = ARM_systick_elapsed_ticks(pthisSV_9DOF_GBY_KALMAN->systick);
149  }
150 #endif
151  return;
152 }
153 
154 void fInit_1DOF_P_BASIC(struct SV_1DOF_P_BASIC *pthisSV,
155  struct PressureSensor *pthisPressure, float flpftimesecs)
156 {
157  // set algorithm sampling interval (typically 40Hz) and low pass filter
158  // Note: the MPL3115 sensor only updates its output every 1s and is therefore repeatedly oversampled at 40Hz
159  // but executing the exponenial filter at the 40Hz rate also performs an interpolation giving smoother output.
160  // set algorithm sampling interval (typically 40Hz)
161  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
162 
163  // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
164  if (flpftimesecs > pthisSV->fdeltat)
165  pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
166  else
167  pthisSV->flpf = 1.0F;
168 
169  // initialize the low pass filters to current measurement
170  pthisSV->fLPH = pthisPressure->fH;
171  pthisSV->fLPT = pthisPressure->fT;
172 
173  // clear the reset flag
174  pthisSV->resetflag = false;
175 
176  return;
177 } // end fInit_1DOF_P_BASIC
178 
179 void fInit_3DOF_G_BASIC(struct SV_3DOF_G_BASIC *pthisSV,
180  struct AccelSensor *pthisAccel, float flpftimesecs)
181 {
182  // set algorithm sampling interval (typically 40Hz)
183  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
184 
185  // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
186  if (flpftimesecs > pthisSV->fdeltat)
187  pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
188  else
189  pthisSV->flpf = 1.0F;
190 
191  // apply the tilt estimation algorithm to initialize the low pass orientation matrix and quaternion
192 #if THISCOORDSYSTEM == NED
193  f3DOFTiltNED(pthisSV->fLPR, pthisAccel->fGc);
194 #elif THISCOORDSYSTEM == ANDROID
195  f3DOFTiltAndroid(pthisSV->fLPR, pthisAccel->fGc);
196 #else // WIN8
197  f3DOFTiltWin8(pthisSV->fLPR, pthisAccel->fGc);
198 #endif
199  fQuaternionFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPq));
200 
201  // clear the reset flag
202  pthisSV->resetflag = false;
203 
204  return;
205 } // end fInit_3DOF_G_BASIC
206 
207 void fInit_3DOF_B_BASIC(struct SV_3DOF_B_BASIC *pthisSV,
208  struct MagSensor *pthisMag, float flpftimesecs)
209 {
210  // set algorithm sampling interval (typically 40Hz)
211  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
212 
213  // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
214  if (flpftimesecs > pthisSV->fdeltat)
215  pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
216  else
217  pthisSV->flpf = 1.0F;
218 
219  // initialize the low pass filtered magnetometer orientation matrix and quaternion using fBc
220 #if THISCOORDSYSTEM == NED
221  f3DOFMagnetometerMatrixNED(pthisSV->fLPR, pthisMag->fBc);
222 #elif THISCOORDSYSTEM == ANDROID
223  f3DOFMagnetometerMatrixAndroid(pthisSV->fLPR, pthisMag->fBc);
224 #else // WIN8
225  f3DOFMagnetometerMatrixWin8(pthisSV->fLPR, pthisMag->fBc);
226 #endif
227  fQuaternionFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPq));
228 
229  // clear the reset flag
230  pthisSV->resetflag = false;
231 
232  return;
233 } // end fInit_3DOF_B_BASIC
234 
235 void fInit_3DOF_Y_BASIC(struct SV_3DOF_Y_BASIC *pthisSV)
236 {
237  // compute the sampling time intervals (secs)
238  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
239 
240  // initialize orientation estimate to flat
241  f3x3matrixAeqI(pthisSV->fR);
242  fqAeq1(&(pthisSV->fq));
243 
244  // clear the reset flag
245  pthisSV->resetflag = false;
246 
247  return;
248 } // end fInit_3DOF_Y_BASIC
249 
251  struct AccelSensor *pthisAccel,
252  struct MagSensor *pthisMag, float flpftimesecs)
253 {
254  float ftmp;
255 
256  // set algorithm sampling interval (typically 40Hz)
257  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
258 
259  // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
260  if (flpftimesecs > pthisSV->fdeltat)
261  pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
262  else
263  pthisSV->flpf = 1.0F;
264 
265  // initialize the instantaneous orientation matrix, inclination angle and quaternion
266 #if THISCOORDSYSTEM == NED
267  feCompassNED(pthisSV->fLPR, &(pthisSV->fLPDelta), &ftmp, &ftmp, pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp );
268 #elif THISCOORDSYSTEM == ANDROID
269  feCompassAndroid(pthisSV->fLPR, &(pthisSV->fLPDelta), &ftmp, &ftmp, pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
270 #else // WIN8
271  feCompassWin8(pthisSV->fLPR, &(pthisSV->fLPDelta), &ftmp, &ftmp, pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
272 #endif
273  fQuaternionFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPq));
274 
275  // clear the reset flag
276  pthisSV->resetflag = false;
277 
278  return;
279 } // end fInit_6DOF_GB_BASIC
280 
281 // function initalizes the 6DOF accel + gyro Kalman filter algorithm
283  struct AccelSensor *pthisAccel,
284  struct GyroSensor *pthisGyro)
285 {
286  float *pFlash; // pointer to flash float words
287  int8 i; // loop counter
288 
289  // compute and store useful product terms to save floating point calculations later
290  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
291  pthisSV->fQwbOver3 = FQWB_6DOF_GY_KALMAN / 3.0F;
292  pthisSV->fAlphaOver2 = FPIOVER180 * pthisSV->fdeltat / 2.0F;
293  pthisSV->fAlphaSqOver4 = pthisSV->fAlphaOver2 * pthisSV->fAlphaOver2;
294  pthisSV->fAlphaQwbOver6 = pthisSV->fAlphaOver2 * pthisSV->fQwbOver3;
296  pthisSV->fMaxGyroOffsetChange = sqrtf(fabs(FQWB_6DOF_GY_KALMAN)) / (float)FUSION_HZ;
297 
298  // zero the a posteriori gyro offset and error vectors
299  for (i = CHX; i <= CHZ; i++)
300  {
301  pthisSV->fqgErrPl[i] = 0.0F;
302  pthisSV->fbErrPl[i] = 0.0F;
303  }
304 
305  // check to see if a gyro calibration exists in flash
306  // the standard value for erased flash is 0xFF in each byte but for portability check against 0x12345678
307 #ifndef SIMULATION
308  pFlash = (float *) (CALIBRATION_NVM_ADDR + GYRO_NVM_OFFSET);
309  if (*((uint32 *) pFlash++) == 0x12345678)
310  {
311  // copy the gyro calibration from flash into the state vector
312  for (i = CHX; i <= CHZ; i++) pthisSV->fbPl[i] = *(pFlash++);
313  }
314  else
315  {
316 #endif
317  // set the gyro offset to the current measurement if within limits
318  for (i = CHX; i <= CHZ; i++)
319  {
320  if ((pthisGyro->fYs[i] >= FMIN_6DOF_GY_BPL) &&
321  (pthisGyro->fYs[i] <= FMAX_6DOF_GY_BPL))
322  pthisSV->fbPl[i] = pthisGyro->fYs[i];
323  else
324  pthisSV->fbPl[i] = 0.0F;
325  }
326 #ifndef SIMULATION
327  }
328 #endif
329  // initialize the a posteriori orientation state vector to the tilt orientation
330 #if THISCOORDSYSTEM == NED
331  f3DOFTiltNED(pthisSV->fRPl, pthisAccel->fGc);
332 #elif THISCOORDSYSTEM == ANDROID
333  f3DOFTiltAndroid(pthisSV->fRPl, pthisAccel->fGc);
334 #else // Win8
335  f3DOFTiltWin8(pthisSV->fRPl, pthisAccel->fGc);
336 #endif
337  fQuaternionFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fqPl));
338 
339  // clear the reset flag
340  pthisSV->resetflag = false;
341 
342  return;
343 } // end fInit_6DOF_GY_KALMAN
344 
345 // function initializes the 9DOF Kalman filter
346 void fInit_9DOF_GBY_KALMAN(struct SV_9DOF_GBY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag,
347  struct GyroSensor *pthisGyro, struct MagCalibration *pthisMagCal)
348 {
349  float ftmp;// scratch
350  float *pFlash;// pointer to flash float words
351  int8 i;// loop counter
352 
353  // compute and store useful product terms to save floating point calculations later
354  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
355  pthisSV->fgdeltat = GTOMSEC2 * pthisSV->fdeltat;
356  pthisSV->fQwbOver3 = FQWB_9DOF_GBY_KALMAN / 3.0F;
357  pthisSV->fAlphaOver2 = FPIOVER180 * pthisSV->fdeltat / 2.0F;
358  pthisSV->fAlphaSqOver4 = pthisSV->fAlphaOver2 * pthisSV->fAlphaOver2;
359  pthisSV->fAlphaQwbOver6 = pthisSV->fAlphaOver2 * pthisSV->fQwbOver3;
361  pthisSV->fMaxGyroOffsetChange = sqrtf(fabs(FQWB_9DOF_GBY_KALMAN)) / (float)FUSION_HZ;
362 
363  // zero the a posteriori error vectors and inertial outputs
364  for (i = CHX; i <= CHZ; i++) {
365  pthisSV->fqgErrPl[i] = 0.0F;
366  pthisSV->fqmErrPl[i] = 0.0F;
367  pthisSV->fbErrPl[i] = 0.0F;
368  pthisSV->fVelGl[i] = 0.0F;
369  pthisSV->fDisGl[i] = 0.0F;
370  }
371 
372  // check to see if a gyro calibration exists in flash
373  // the standard value for erased flash is 0xFF in each byte but for portability check against 0x12345678
374 #ifndef SIMULATION
375  pFlash = (float *) (CALIBRATION_NVM_ADDR + GYRO_NVM_OFFSET);
376  if (*((uint32*) pFlash++) == 0x12345678) {
377  // copy the gyro calibration from flash into the state vector
378  for (i = CHX; i <= CHZ; i++)
379  pthisSV->fbPl[i] = *(pFlash++);
380  } else {
381 #endif
382  // set the gyro offset to the current measurement if within limits
383  for (i = CHX; i <= CHZ; i++) {
384  if ((pthisGyro->fYs[i] >= FMIN_9DOF_GBY_BPL) && (pthisGyro->fYs[i] <= FMAX_9DOF_GBY_BPL))
385  pthisSV->fbPl[i] = pthisGyro->fYs[i];
386  else
387  pthisSV->fbPl[i] = 0.0F;
388  }
389 #ifndef SIMULATION
390  }
391 #endif
392 
393  // initialize the posteriori orientation state vector to the instantaneous eCompass orientation
394  pthisSV->iFirstAccelMagLock = false;
395 #if THISCOORDSYSTEM == NED
396  feCompassNED(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
397  pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
398 #elif THISCOORDSYSTEM == ANDROID
399  feCompassAndroid(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
400  pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
401 #else // WIN8
402  feCompassWin8(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
403  pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
404 #endif
405  fQuaternionFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fqPl));
406 
407  // clear the reset flag
408  pthisSV->resetflag = false;
409 
410  return;
411 } // end fInit_9DOF_GBY_KALMAN
412 
413 //////////////////////////////////////////////////////////////////////////////////////////////////
414 
415 // run time functions for the sensor fusion algorithms
416 //////////////////////////////////////////////////////////////////////////////////////////////////
417 
418 // 1DOF pressure function
419 void fRun_1DOF_P_BASIC(struct SV_1DOF_P_BASIC *pthisSV,
420  struct PressureSensor *pthisPressure)
421 {
422  // if requested, do a reset and return
423  if (pthisSV->resetflag)
424  {
425  fInit_1DOF_P_BASIC(pthisSV, pthisPressure, FLPFSECS_1DOF_P_BASIC);
426  return;
427  }
428 
429  // exponentially low pass filter the pressure and temperature.
430  // when executed at (typically) 40Hz, this filter interpolates the raw signals which are
431  // output every 1s in Auto Acquisition mode.
432  pthisSV->fLPH += pthisSV->flpf * (pthisPressure->fH - pthisSV->fLPH);
433  pthisSV->fLPT += pthisSV->flpf * (pthisPressure->fT - pthisSV->fLPT);
434 
435  return;
436 } // end fRun_1DOF_P_BASIC
437 
438 // 3DOF orientation function which calls tilt functions and implements low pass filters
439 void fRun_3DOF_G_BASIC(struct SV_3DOF_G_BASIC *pthisSV,
440  struct AccelSensor *pthisAccel)
441 {
442  // if requested, do a reset and return
443  if (pthisSV->resetflag)
444  {
445  fInit_3DOF_G_BASIC(pthisSV, pthisAccel, FLPFSECS_3DOF_G_BASIC);
446  return;
447  }
448 
449  // apply the tilt estimation algorithm to get the instantaneous orientation matrix
450 #if THISCOORDSYSTEM == NED
451  f3DOFTiltNED(pthisSV->fR, pthisAccel->fGc);
452 #elif THISCOORDSYSTEM == ANDROID
453  f3DOFTiltAndroid(pthisSV->fR, pthisAccel->fGc);
454 #else // WIN8
455  f3DOFTiltWin8(pthisSV->fR, pthisAccel->fGc);
456 #endif
457 
458  // compute the instantaneous quaternion and low pass filter
459  fQuaternionFromRotationMatrix(pthisSV->fR, &(pthisSV->fq));
460  fLPFOrientationQuaternion(&(pthisSV->fq), &(pthisSV->fLPq), pthisSV->flpf,
461  pthisSV->fdeltat, pthisSV->fOmega);
462 
463  // compute the low pass rotation matrix and rotation vector from low pass filtered quaternion
464  fRotationMatrixFromQuaternion(pthisSV->fLPR, &(pthisSV->fLPq));
465  fRotationVectorDegFromQuaternion(&(pthisSV->fLPq), pthisSV->fLPRVec);
466 
467  // calculate the Euler angles from the low pass orientation matrix
468 #if THISCOORDSYSTEM == NED
469  fNEDAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
470  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
471  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
472 #elif THISCOORDSYSTEM == ANDROID
473  fAndroidAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
474  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
475  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
476 #else // WIN8
477  fWin8AnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
478  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
479  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
480 #endif
481 
482  // force the yaw and compass angles to zero
483  pthisSV->fLPPsi = pthisSV->fLPRho = 0.0F;
484 
485  return;
486 } // end fRun_3DOF_G_BASIC
487 
488 // 2D automobile eCompass
489 void fRun_3DOF_B_BASIC(struct SV_3DOF_B_BASIC *pthisSV, struct MagSensor *pthisMag)
490 {
491  // if requested, do a reset and return
492  if (pthisSV->resetflag)
493  {
494  fInit_3DOF_B_BASIC(pthisSV, pthisMag, FLPFSECS_3DOF_B_BASIC);
495  return;
496  }
497 
498  // calculate the 3DOF magnetometer orientation matrix from fBc
499 #if THISCOORDSYSTEM == NED
500  f3DOFMagnetometerMatrixNED(pthisSV->fR, pthisMag->fBc);
501 #elif THISCOORDSYSTEM == ANDROID
502  f3DOFMagnetometerMatrixAndroid(pthisSV->fR, pthisMag->fBc);
503 #else // WIN8
504  f3DOFMagnetometerMatrixWin8(pthisSV->fR, pthisMag->fBc);
505 #endif
506 
507  // compute the instantaneous quaternion and low pass filter
508  fQuaternionFromRotationMatrix(pthisSV->fR, &(pthisSV->fq));
509  fLPFOrientationQuaternion(&(pthisSV->fq), &(pthisSV->fLPq), pthisSV->flpf,
510  pthisSV->fdeltat, pthisSV->fOmega);
511 
512  // compute the low pass rotation matrix and rotation vector from low pass filtered quaternion
513  fRotationMatrixFromQuaternion(pthisSV->fLPR, &(pthisSV->fLPq));
514  fRotationVectorDegFromQuaternion(&(pthisSV->fLPq), pthisSV->fLPRVec);
515 
516  // calculate the Euler angles from the low pass orientation matrix
517 #if THISCOORDSYSTEM == NED
518  fNEDAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
519  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
520  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
521 #elif THISCOORDSYSTEM == ANDROID
522  fAndroidAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
523  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
524  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
525 #else // WIN8
526  fWin8AnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
527  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
528  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
529 #endif
530  return;
531 }
532 
533 // basic gyro integration function
534 void fRun_3DOF_Y_BASIC(struct SV_3DOF_Y_BASIC *pthisSV,
535  struct GyroSensor *pthisGyro)
536 {
537  Quaternion ftmpq; // scratch quaternion
538  int8 i; // loop counter
539 
540  // if requested, do a reset and return
541  if (pthisSV->resetflag)
542  {
543  fInit_3DOF_Y_BASIC(pthisSV);
544  return;
545  }
546 
547  // perform an approximate gyro integration for this algorithm using the average of all gyro measurments
548  // in the FIFO rather than computing the products of the individual quaternions.
549  // the reason is this algorithm does not estimate and subtract the gyro offset so any loss of integration accuracy
550  // from using the average gyro measurement is irrelevant.
551  // calculate the angular velocity (deg/s) and rotation vector from the average measurement
552  for (i = CHX; i <= CHZ; i++) pthisSV->fOmega[i] = pthisGyro->fYs[i];
553 
554  // compute the incremental rotation quaternion ftmpq, rotate the orientation quaternion fq
555  // and re-normalize fq to prevent error propagation
556  fQuaternionFromRotationVectorDeg(&ftmpq, pthisSV->fOmega, pthisSV->fdeltat);
557  qAeqAxB(&(pthisSV->fq), &ftmpq);
558  fqAeqNormqA(&(pthisSV->fq));
559 
560  // get the rotation matrix and rotation vector from the orientation quaternion fq
561  fRotationMatrixFromQuaternion(pthisSV->fR, &(pthisSV->fq));
562  fRotationVectorDegFromQuaternion(&(pthisSV->fq), pthisSV->fRVec);
563 
564  // compute the Euler angles from the orientation matrix
565 #if THISCOORDSYSTEM == NED
566  fNEDAnglesDegFromRotationMatrix(pthisSV->fR, &(pthisSV->fPhi),
567  &(pthisSV->fThe), &(pthisSV->fPsi),
568  &(pthisSV->fRho), &(pthisSV->fChi));
569 #elif THISCOORDSYSTEM == ANDROID
570  fAndroidAnglesDegFromRotationMatrix(pthisSV->fR, &(pthisSV->fPhi),
571  &(pthisSV->fThe), &(pthisSV->fPsi),
572  &(pthisSV->fRho), &(pthisSV->fChi));
573 #else // WIN8
574  fWin8AnglesDegFromRotationMatrix(pthisSV->fR, &(pthisSV->fPhi),
575  &(pthisSV->fThe), &(pthisSV->fPsi),
576  &(pthisSV->fRho), &(pthisSV->fChi));
577 #endif
578  return;
579 } // end fRun_3DOF_Y_BASIC
580 
581 // 6DOF eCompass orientation function
583  struct MagSensor *pthisMag, struct AccelSensor *pthisAccel)
584 {
585  float ftmp1, ftmp2, ftmp3, ftmp4;
586 
587  // if requested, do a reset and return
588  if (pthisSV->resetflag)
589  {
590  fInit_6DOF_GB_BASIC(pthisSV, pthisAccel, pthisMag,
592  return;
593  }
594 
595  // call the eCompass algorithm to get the instantaneous orientation matrix and inclination angle
596 #if THISCOORDSYSTEM == NED
597  feCompassNED(pthisSV->fR, &(pthisSV->fDelta), &ftmp1, &ftmp2, pthisMag->fBc, pthisAccel->fGc, &ftmp3, &ftmp4);
598 #elif THISCOORDSYSTEM == ANDROID
599  feCompassAndroid(pthisSV->fR, &(pthisSV->fDelta), &ftmp1, &ftmp2, pthisMag->fBc, pthisAccel->fGc, &ftmp3, &ftmp4);
600 #else // WIN8
601  feCompassWin8(pthisSV->fR, &(pthisSV->fDelta), &ftmp1, &ftmp2, pthisMag->fBc, pthisAccel->fGc, &ftmp3, &ftmp4);
602 #endif
603 
604  // compute the instantaneous quaternion and low pass filter
605  fQuaternionFromRotationMatrix(pthisSV->fR, &(pthisSV->fq));
606  fLPFOrientationQuaternion(&(pthisSV->fq), &(pthisSV->fLPq), pthisSV->flpf,
607  pthisSV->fdeltat, pthisSV->fOmega);
608 
609  // compute the low pass rotation matrix and rotation vector from low pass filtered quaternion
610  fRotationMatrixFromQuaternion(pthisSV->fLPR, &(pthisSV->fLPq));
611  fRotationVectorDegFromQuaternion(&(pthisSV->fLPq), pthisSV->fLPRVec);
612 
613  // calculate the Euler angles from the low pass orientation matrix
614 #if THISCOORDSYSTEM == NED
615  fNEDAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
616  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
617  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
618 #elif THISCOORDSYSTEM == ANDROID
619  fAndroidAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
620  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
621  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
622 #else // WIN8
623  fWin8AnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
624  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
625  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
626 #endif
627 
628  // low pass filter the geomagnetic inclination angle with a simple exponential filter
629  pthisSV->fLPDelta += pthisSV->flpf * (pthisSV->fDelta - pthisSV->fLPDelta);
630 
631  return;
632 } // end fRun_6DOF_GB_BASIC
633 
634 // 6DOF accelerometer+gyroscope orientation function implemented using indirect complementary Kalman filter
636  struct AccelSensor *pthisAccel,
637  struct GyroSensor *pthisGyro)
638 {
639  // local scalars and arrays
640  float ftmpMi3x1[3]; // temporary vector used for a priori calculations
641  float ftmp3DOF3x1[3]; // temporary vector used for 3DOF calculations
642  float ftmpA3x3[3][3]; // scratch 3x3 matrix
643  float fQvGQa; // accelerometer noise covariance to 1g sphere
644  float fC3x6ik; // element i, k of measurement matrix C
645  float fC3x6jk; // element j, k of measurement matrix C
646  float fmodGc; // modulus of fGc[]
647  Quaternion fqMi; // a priori orientation quaternion
648  Quaternion ftmpq; // scratch quaternion
649  float ftmp; // scratch float
650  int8 ierror; // matrix inversion error flag
651  int8 i,
652  j,
653  k; // loop counters
654 
655  // working arrays for 3x3 matrix inversion
656  float *pfRows[3];
657  int8 iColInd[3];
658  int8 iRowInd[3];
659  int8 iPivot[3];
660 
661  // if requested, do a reset initialization with no further processing
662  if (pthisSV->resetflag)
663  {
664  fInit_6DOF_GY_KALMAN(pthisSV, pthisAccel, pthisGyro);
665  return;
666  }
667 
668  // compute the average angular velocity (used for display only) from the average measurement minus gyro offset
669  for (i = CHX; i <= CHZ; i++)
670  pthisSV->fOmega[i] = (float) pthisGyro->iYs[i] *
671  pthisGyro->fDegPerSecPerCount -
672  pthisSV->fbPl[i];
673 
674  // initialize the a priori orientation quaternion fqMi to the previous iteration's a posteriori estimate
675  // and incrementally rotate fqMi by the contents of the gyro FIFO buffer
676  fqMi = pthisSV->fqPl;
677  if (pthisGyro->iFIFOCount > 0)
678  {
679  // set ftmp to the interval between the FIFO gyro measurements
680  ftmp = pthisSV->fdeltat / (float) pthisGyro->iFIFOCount;
681 
682  // normal case, loop over all the buffered gyroscope measurements
683  for (j = 0; j < pthisGyro->iFIFOCount; j++)
684  {
685  // calculate the instantaneous angular velocity subtracting the gyro offset
686  for (i = CHX; i <= CHZ; i++)
687  ftmpMi3x1[i] = (float) pthisGyro->iYsFIFO[j][i] *
688  pthisGyro->fDegPerSecPerCount -
689  pthisSV->fbPl[i];
690 
691  // compute the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
692  fQuaternionFromRotationVectorDeg(&ftmpq, ftmpMi3x1, ftmp);
693  qAeqAxB(&fqMi, &ftmpq);
694  }
695  }
696  else
697  {
698  // special case with no new FIFO measurements, use the previous iteration's average gyro reading to compute
699  // the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
700  fQuaternionFromRotationVectorDeg(&ftmpq, pthisSV->fOmega,
701  pthisSV->fdeltat);
702  qAeqAxB(&fqMi, &ftmpq);
703  }
704 
705  // set ftmp3DOF3x1 to the 3DOF gravity vector in the sensor frame
706  fmodGc = sqrtf(fabs(pthisAccel->fGc[CHX] * pthisAccel->fGc[CHX] +
707  pthisAccel->fGc[CHY] * pthisAccel->fGc[CHY] +
708  pthisAccel->fGc[CHZ] * pthisAccel->fGc[CHZ]));
709  if (fmodGc != 0.0F)
710  {
711  // normal non-freefall case
712  ftmp = 1.0F / fmodGc;
713  ftmp3DOF3x1[CHX] = pthisAccel->fGc[CHX] * ftmp;
714  ftmp3DOF3x1[CHY] = pthisAccel->fGc[CHY] * ftmp;
715  ftmp3DOF3x1[CHZ] = pthisAccel->fGc[CHZ] * ftmp;
716  }
717  else
718  {
719  // use zero tilt in case of freefall
720  ftmp3DOF3x1[CHX] = 0.0F;
721  ftmp3DOF3x1[CHY] = 0.0F;
722  ftmp3DOF3x1[CHZ] = 1.0F;
723  }
724 
725  // correct accelerometer gravity vector for different coordinate systems
726 #if THISCOORDSYSTEM == NED
727  // +1g in accelerometer z axis (z down) when PCB is flat so no correction needed
728 #elif THISCOORDSYSTEM == ANDROID
729  // +1g in accelerometer z axis (z up) when PCB is flat so negate the vector to obtain gravity
730  ftmp3DOF3x1[CHX] = -ftmp3DOF3x1[CHX];
731  ftmp3DOF3x1[CHY] = -ftmp3DOF3x1[CHY];
732  ftmp3DOF3x1[CHZ] = -ftmp3DOF3x1[CHZ];
733 #else // WIN8
734  // -1g in accelerometer z axis (z up) when PCB is flat so no correction needed
735 #endif
736 
737  // set ftmpMi3x1 to the a priori gravity vector in the sensor frame from the a priori quaternion
738  ftmpMi3x1[CHX] = 2.0F * (fqMi.q1 * fqMi.q3 - fqMi.q0 * fqMi.q2);
739  ftmpMi3x1[CHY] = 2.0F * (fqMi.q2 * fqMi.q3 + fqMi.q0 * fqMi.q1);
740  ftmpMi3x1[CHZ] = 2.0F * (fqMi.q0 * fqMi.q0 + fqMi.q3 * fqMi.q3) - 1.0F;
741 
742  // correct a priori gravity vector for different coordinate systems
743 #if THISCOORDSYSTEM == NED
744  // z axis is down (NED) so no correction needed
745 #else // ANDROID and WIN8
746  // z axis is up (ANDROID and WIN8 ENU) so no negate the vector to obtain gravity
747  ftmpMi3x1[CHX] = -ftmpMi3x1[CHX];
748  ftmpMi3x1[CHY] = -ftmpMi3x1[CHY];
749  ftmpMi3x1[CHZ] = -ftmpMi3x1[CHZ];
750 #endif
751 
752  // calculate the rotation quaternion between 3DOF and a priori gravity vectors (ignored minus signs cancel here)
753  // and copy the quaternion vector components to the measurement error vector fZErr
754  fveqconjgquq(&ftmpq, ftmp3DOF3x1, ftmpMi3x1);
755  pthisSV->fZErr[CHX] = ftmpq.q1;
756  pthisSV->fZErr[CHY] = ftmpq.q2;
757  pthisSV->fZErr[CHZ] = ftmpq.q3;
758 
759  // update Qw using the a posteriori error vectors from the previous iteration.
760  // as Qv increases or Qw decreases, K -> 0 and the Kalman filter is weighted towards the a priori prediction
761  // as Qv decreases or Qw increases, KC -> I and the Kalman filter is weighted towards the measurement.
762  // initialize Qw to all zeroes
763  for (i = 0; i < 6; i++)
764  for (j = 0; j < 6; j++) pthisSV->fQw6x6[i][j] = 0.0F;
765 
766  // partial diagonal gyro offset terms
767  pthisSV->fQw6x6[3][3] = pthisSV->fbErrPl[CHX] * pthisSV->fbErrPl[CHX];
768  pthisSV->fQw6x6[4][4] = pthisSV->fbErrPl[CHY] * pthisSV->fbErrPl[CHY];
769  pthisSV->fQw6x6[5][5] = pthisSV->fbErrPl[CHZ] * pthisSV->fbErrPl[CHZ];
770 
771  // diagonal gravity vector components
772  pthisSV->fQw6x6[0][0] = pthisSV->fqgErrPl[CHX] *
773  pthisSV->fqgErrPl[CHX] +
774  pthisSV->fAlphaSqQvYQwbOver12 +
775  pthisSV->fAlphaSqOver4 *
776  pthisSV->fQw6x6[3][3];
777  pthisSV->fQw6x6[1][1] = pthisSV->fqgErrPl[CHY] *
778  pthisSV->fqgErrPl[CHY] +
779  pthisSV->fAlphaSqQvYQwbOver12 +
780  pthisSV->fAlphaSqOver4 *
781  pthisSV->fQw6x6[4][4];
782  pthisSV->fQw6x6[2][2] = pthisSV->fqgErrPl[CHZ] *
783  pthisSV->fqgErrPl[CHZ] +
784  pthisSV->fAlphaSqQvYQwbOver12 +
785  pthisSV->fAlphaSqOver4 *
786  pthisSV->fQw6x6[5][5];
787 
788  // remaining diagonal gyro offset components
789  pthisSV->fQw6x6[3][3] += pthisSV->fQwbOver3;
790  pthisSV->fQw6x6[4][4] += pthisSV->fQwbOver3;
791  pthisSV->fQw6x6[5][5] += pthisSV->fQwbOver3;
792 
793  // off diagonal gravity and gyro offset components
794  pthisSV->fQw6x6[0][3] = pthisSV->fQw6x6[3][0] = pthisSV->fqgErrPl[CHX] *
795  pthisSV->fbErrPl[CHX] -
796  pthisSV->fAlphaOver2 *
797  pthisSV->fQw6x6[3][3];
798  pthisSV->fQw6x6[1][4] = pthisSV->fQw6x6[4][1] = pthisSV->fqgErrPl[CHY] *
799  pthisSV->fbErrPl[CHY] -
800  pthisSV->fAlphaOver2 *
801  pthisSV->fQw6x6[4][4];
802  pthisSV->fQw6x6[2][5] = pthisSV->fQw6x6[5][2] = pthisSV->fqgErrPl[CHZ] *
803  pthisSV->fbErrPl[CHZ] -
804  pthisSV->fAlphaOver2 *
805  pthisSV->fQw6x6[5][5];
806 
807  // calculate the vector fQv containing the diagonal elements of the measurement covariance matrix Qv
808  ftmp = fmodGc - 1.0F;
809  fQvGQa = 3.0F * ftmp * ftmp;
810  if (fQvGQa < FQVG_6DOF_GY_KALMAN) fQvGQa = FQVG_6DOF_GY_KALMAN;
811  pthisSV->fQv = ONEOVER12 * fQvGQa + pthisSV->fAlphaSqQvYQwbOver12;
812 
813  // calculate the 6x3 Kalman gain matrix K = Qw * C^T * inv(C * Qw * C^T + Qv)
814  // set fQwCT6x3 = Qw.C^T where Qw has size 6x6 and C^T has size 6x3
815  for (i = 0; i < 6; i++) // loop over rows
816  {
817  for (j = 0; j < 3; j++) // loop over columns
818  {
819  pthisSV->fQwCT6x3[i][j] = 0.0F;
820 
821  // accumulate matrix sum
822  for (k = 0; k < 6; k++)
823  {
824  // determine fC3x6[j][k] since the matrix is highly sparse
825  fC3x6jk = 0.0F;
826  if (k == j) fC3x6jk = 1.0F;
827  if (k == (j + 3)) fC3x6jk = -pthisSV->fAlphaOver2;
828 
829  // accumulate fQwCT6x3[i][j] += Qw6x6[i][k] * C[j][k]
830  if ((pthisSV->fQw6x6[i][k] != 0.0F) && (fC3x6jk != 0.0F))
831  {
832  if (fC3x6jk == 1.0F)
833  pthisSV->fQwCT6x3[i][j] += pthisSV->fQw6x6[i][k];
834  else
835  pthisSV->fQwCT6x3[i][j] += pthisSV->fQw6x6[i][k] * fC3x6jk;
836  }
837  }
838  }
839  }
840 
841  // set symmetric ftmpA3x3 = C.(Qw.C^T) + Qv = C.fQwCT6x3 + Qv
842  for (i = 0; i < 3; i++) // loop over rows
843  {
844  for (j = i; j < 3; j++) // loop over on and above diagonal columns
845  {
846  // zero off diagonal and set diagonal to Qv
847  if (i == j)
848  ftmpA3x3[i][j] = pthisSV->fQv;
849  else
850  ftmpA3x3[i][j] = 0.0F;
851 
852  // accumulate matrix sum
853  for (k = 0; k < 6; k++)
854  {
855  // determine fC3x6[i][k]
856  fC3x6ik = 0.0F;
857  if (k == i) fC3x6ik = 1.0F;
858  if (k == (i + 3)) fC3x6ik = -pthisSV->fAlphaOver2;
859 
860  // accumulate ftmpA3x3[i][j] += C[i][k] & fQwCT6x3[k][j]
861  if ((fC3x6ik != 0.0F) && (pthisSV->fQwCT6x3[k][j] != 0.0F))
862  {
863  if (fC3x6ik == 1.0F)
864  ftmpA3x3[i][j] += pthisSV->fQwCT6x3[k][j];
865  else
866  ftmpA3x3[i][j] += fC3x6ik * pthisSV->fQwCT6x3[k][j];
867  }
868  }
869  }
870  }
871 
872  // set ftmpA3x3 below diagonal elements to above diagonal elements
873  ftmpA3x3[1][0] = ftmpA3x3[0][1];
874  ftmpA3x3[2][0] = ftmpA3x3[0][2];
875  ftmpA3x3[2][1] = ftmpA3x3[1][2];
876 
877  // invert ftmpA3x3 in situ to give ftmpA3x3 = inv(C * Qw * C^T + Qv) = inv(ftmpA3x3)
878  for (i = 0; i < 3; i++) pfRows[i] = ftmpA3x3[i];
879  fmatrixAeqInvA(pfRows, iColInd, iRowInd, iPivot, 3, &ierror);
880 
881  // on successful inversion set Kalman gain matrix fK6x3 = Qw * C^T * inv(C * Qw * C^T + Qv) = fQwCT6x3 * ftmpA3x3
882  if (!ierror)
883  {
884  // normal case
885  for (i = 0; i < 6; i++) // loop over rows
886  {
887  for (j = 0; j < 3; j++) // loop over columns
888  {
889  pthisSV->fK6x3[i][j] = 0.0F;
890  for (k = 0; k < 3; k++)
891  {
892  if ((pthisSV->fQwCT6x3[i][k] != 0.0F) &&
893  (ftmpA3x3[k][j] != 0.0F))
894  {
895  pthisSV->fK6x3[i][j] += pthisSV->fQwCT6x3[i][k] * ftmpA3x3[k][j];
896  }
897  }
898  }
899  }
900  }
901  else
902  {
903  // ftmpA3x3 was singular so set Kalman gain matrix fK6x3 to zero
904  for (i = 0; i < 6; i++) // loop over rows
905  {
906  for (j = 0; j < 3; j++) // loop over columns
907  {
908  pthisSV->fK6x3[i][j] = 0.0F;
909  }
910  }
911  }
912 
913  // calculate the a posteriori gravity and geomagnetic tilt quaternion errors and gyro offset error vector
914  // from the Kalman matrix fK6x3 and from the measurement error vector fZErr.
915  for (i = CHX; i <= CHZ; i++)
916  {
917  // gravity tilt vector error component
918  if (pthisSV->fK6x3[i][CHX] != 0.0F)
919  pthisSV->fqgErrPl[i] = pthisSV->fK6x3[i][CHX] * pthisSV->fZErr[CHX];
920  else
921  pthisSV->fqgErrPl[i] = 0.0F;
922  if (pthisSV->fK6x3[i][CHY] != 0.0F)
923  pthisSV->fqgErrPl[i] += pthisSV->fK6x3[i][CHY] * pthisSV->fZErr[CHY];
924  if (pthisSV->fK6x3[i][CHZ] != 0.0F)
925  pthisSV->fqgErrPl[i] += pthisSV->fK6x3[i][CHZ] * pthisSV->fZErr[CHZ];
926 
927  // gyro offset vector error component
928  if (pthisSV->fK6x3[i + 3][CHX] != 0.0F)
929  pthisSV->fbErrPl[i] = pthisSV->fK6x3[i + 3][CHX] * pthisSV->fZErr[CHX];
930  else
931  pthisSV->fbErrPl[i] = 0.0F;
932  if (pthisSV->fK6x3[i + 3][CHY] != 0.0F)
933  pthisSV->fbErrPl[i] += pthisSV->fK6x3[i + 3][CHY] * pthisSV->fZErr[CHY];
934  if (pthisSV->fK6x3[i + 3][CHZ] != 0.0F)
935  pthisSV->fbErrPl[i] += pthisSV->fK6x3[i + 3][CHZ] * pthisSV->fZErr[CHZ];
936  }
937 
938  // set ftmpq to the gravity tilt correction (conjugate) quaternion
939  ftmpq.q1 = -pthisSV->fqgErrPl[CHX];
940  ftmpq.q2 = -pthisSV->fqgErrPl[CHY];
941  ftmpq.q3 = -pthisSV->fqgErrPl[CHZ];
942  ftmp = ftmpq.q1 * ftmpq.q1 + ftmpq.q2 * ftmpq.q2 + ftmpq.q3 * ftmpq.q3;
943 
944  // determine the scalar component q0 to enforce normalization
945  if (ftmp <= 1.0F)
946  {
947  // normal case
948  ftmpq.q0 = sqrtf(fabsf(1.0F - ftmp));
949  }
950  else
951  {
952  // if vector component exceeds unity then set to 180 degree rotation and force normalization
953  ftmp = 1.0F / sqrtf(ftmp);
954  ftmpq.q0 = 0.0F;
955  ftmpq.q1 *= ftmp;
956  ftmpq.q2 *= ftmp;
957  ftmpq.q3 *= ftmp;
958  }
959 
960  // apply the gravity tilt correction quaternion so fqPl = fqMi.(fqgErrPl)* = fqMi.ftmpq and normalize
961  qAeqBxC(&(pthisSV->fqPl), &fqMi, &ftmpq);
962 
963  // normalize the a posteriori quaternion and compute the a posteriori rotation matrix and rotation vector
964  fqAeqNormqA(&(pthisSV->fqPl));
965  fRotationMatrixFromQuaternion(pthisSV->fRPl, &(pthisSV->fqPl));
966  fRotationVectorDegFromQuaternion(&(pthisSV->fqPl), pthisSV->fRVecPl);
967 
968  // update the a posteriori gyro offset vector: b+[k] = b-[k] - be+[k] = b+[k] - be+[k] (deg/s)
969  // limiting the correction to the maximum permitted by the random walk model
970  for (i = CHX; i <= CHZ; i++)
971  {
972  if (pthisSV->fbErrPl[i] > pthisSV->fMaxGyroOffsetChange)
973  pthisSV->fbPl[i] -= pthisSV->fMaxGyroOffsetChange;
974  else if (pthisSV->fbErrPl[i] < -pthisSV->fMaxGyroOffsetChange)
975  pthisSV->fbPl[i] += pthisSV->fMaxGyroOffsetChange;
976  else
977  pthisSV->fbPl[i] -= pthisSV->fbErrPl[i];
978  }
979 
980  // compute the linear acceleration fAccGl in the global frame
981  // first de-rotate the accelerometer measurement fGc from the sensor to global frame
982  // using the transpose (inverse) of the orientation matrix fRPl
983  fveqRu(pthisSV->fAccGl, pthisSV->fRPl, pthisAccel->fGc, 1);
984 
985  // sutract the fixed gravity vector in the global frame leaving linear acceleration
986 #if THISCOORDSYSTEM == NED
987  // gravity positive NED
988  pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
989  pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
990  pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] - 1.0F);
991 #elif THISCOORDSYSTEM == ANDROID
992  // acceleration positive ENU
993  pthisSV->fAccGl[CHZ] = pthisSV->fAccGl[CHZ] - 1.0F;
994 #else // WIN8
995  // gravity positive ENU
996  pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
997  pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
998  pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] + 1.0F);
999 #endif
1000 
1001  // compute the a posteriori Euler angles from the a posteriori orientation matrix fRPl
1002 #if THISCOORDSYSTEM == NED
1003  fNEDAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl),
1004  &(pthisSV->fThePl), &(pthisSV->fPsiPl),
1005  &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1006 #elif THISCOORDSYSTEM == ANDROID
1007  fAndroidAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl),
1008  &(pthisSV->fThePl), &(pthisSV->fPsiPl),
1009  &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1010 #else // WIN8
1011  fWin8AnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl),
1012  &(pthisSV->fThePl), &(pthisSV->fPsiPl),
1013  &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1014 #endif
1015  return;
1016 } // end fRun_6DOF_GY_KALMAN
1017 #if F_9DOF_GBY_KALMAN
1018 // 9DOF accelerometer+magnetometer+gyroscope orientation function implemented using indirect complementary Kalman filter
1019 void fRun_9DOF_GBY_KALMAN(struct SV_9DOF_GBY_KALMAN *pthisSV,
1020  struct AccelSensor *pthisAccel,
1021  struct MagSensor *pthisMag,
1022  struct GyroSensor *pthisGyro,
1023  struct MagCalibration *pthisMagCal)
1024 {
1025  // local scalars and arrays
1026  float ftmpA6x6[6][6]; // scratch 6x6 matrix
1027  float fRMi[3][3]; // a priori orientation matrix
1028  float fR6DOF[3][3]; // eCompass (6DOF accelerometer+magnetometer) orientation matrix
1029  float fgMi[3]; // a priori estimate of the gravity vector (sensor frame)
1030  float fmMi[3]; // a priori estimate of the geomagnetic vector (sensor frame)
1031  float fgPl[3]; // a posteriori estimate of the gravity vector (sensor frame)
1032  float fmPl[3]; // a posteriori estimate of the geomagnetic vector (sensor frame)
1033  float ftmpA3x3[3][3]; // scratch 3x3 matrix
1034  float ftmpA3x1[3]; // scratch 3x1 vector
1035  float fQvGQa; // accelerometer noise covariance to 1g sphere
1036  float fQvBQd; // magnetometer noise covariance to geomagnetic sphere
1037  float fC6x9ik; // element i, k of measurement matrix C
1038  float fC6x9jk; // element j, k of measurement matrix C
1039  Quaternion fqMi; // a priori orientation quaternion
1040  Quaternion fq6DOF; // eCompass (6DOF accelerometer+magnetometer) orientation quaternion
1041  Quaternion ftmpq; // scratch quaternion used for gyro integration
1042  float fDelta6DOF; // geomagnetic inclination angle computed from accelerometer and magnetometer (deg)
1043  float fsinDelta6DOF; // sin(fDelta6DOF)
1044  float fcosDelta6DOF; // cos(fDelta6DOF)
1045  float fmodGc; // modulus of calibrated accelerometer measurement (g)
1046  float fmodBc; // modulus of calibrated magnetometer measurement (uT)
1047  float ftmp; // scratch float
1048  int8 ierror; // matrix inversion error flag
1049  int8 i,
1050  j,
1051  k; // loop counters
1052 
1053  // working arrays for 6x6 matrix inversion
1054  float *pfRows[6];
1055  int8 iColInd[6];
1056  int8 iRowInd[6];
1057  int8 iPivot[6];
1058 
1059  // if requested, do a reset initialization with no further processing
1060  if (pthisSV->resetflag) {
1061  fInit_9DOF_GBY_KALMAN(pthisSV, pthisAccel, pthisMag, pthisGyro, pthisMagCal);
1062  return;
1063  }
1064 
1065  // compute the average angular velocity (used for display only) from the average measurement minus gyro offset
1066  for (i = CHX; i <= CHZ; i++) pthisSV->fOmega[i] = (float)pthisGyro->iYs[i] * pthisGyro->fDegPerSecPerCount - pthisSV->fbPl[i];
1067 
1068  // initialize the a priori orientation quaternion fqMi to the previous iteration's a posteriori estimate fqPl
1069  // and incrementally rotate fqMi by the contents of the gyro FIFO buffer
1070  fqMi = pthisSV->fqPl;
1071  if (pthisGyro->iFIFOCount > 0) {
1072  // set ftmp to the average interval between FIFO gyro measurements
1073  ftmp = pthisSV->fdeltat / (float)pthisGyro->iFIFOCount;
1074 
1075  // normal case, loop over all the buffered gyroscope measurements
1076  for (j = 0; j < pthisGyro->iFIFOCount; j++) {
1077  // calculate the instantaneous angular velocity subtracting the gyro offset
1078  for (i = CHX; i <= CHZ; i++) ftmpA3x1[i] = (float)pthisGyro->iYsFIFO[j][i] * pthisGyro->fDegPerSecPerCount - pthisSV->fbPl[i];
1079  // compute the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
1080  fQuaternionFromRotationVectorDeg(&ftmpq, ftmpA3x1, ftmp);
1081  qAeqAxB(&fqMi, &ftmpq);
1082  }
1083  } else {
1084  // special case with no new FIFO measurements, use the previous iteration's average gyro reading to compute
1085  // the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
1086  fQuaternionFromRotationVectorDeg(&ftmpq, pthisSV->fOmega, pthisSV->fdeltat);
1087  qAeqAxB(&fqMi, &ftmpq);
1088  }
1089 
1090  // compute the a priori orientation matrix fRMi from the new a priori orientation quaternion fqMi
1091  fRotationMatrixFromQuaternion(fRMi, &fqMi);
1092 
1093  // compute the 6DOF orientation matrix fR6DOF, inclination angle fDelta6DOF and the squared
1094  // deviations of the accelerometer and magnetometer measurements from the 1g gravity and geomagnetic spheres.
1095 #if THISCOORDSYSTEM == NED
1096  feCompassNED(fR6DOF, &fDelta6DOF, &fsinDelta6DOF, &fcosDelta6DOF, pthisMag->fBc, pthisAccel->fGc, &fmodBc, &fmodGc);
1097 #elif THISCOORDSYSTEM == ANDROID
1098  feCompassAndroid(fR6DOF, &fDelta6DOF, &fsinDelta6DOF, &fcosDelta6DOF, pthisMag->fBc, pthisAccel->fGc, &fmodBc, &fmodGc);
1099 #else // WIN8
1100  feCompassWin8(fR6DOF, &fDelta6DOF, &fsinDelta6DOF, &fcosDelta6DOF, pthisMag->fBc, pthisAccel->fGc, &fmodBc, &fmodGc);
1101 #endif
1102 
1103  // compute the 6DOF orientation quaternion fq6DOF from the 6DOF orientation matrix fR6OF
1104  fQuaternionFromRotationMatrix(fR6DOF, &fq6DOF);
1105 
1106  // calculate the acceleration noise variance relative to 1g sphere
1107  ftmp = fmodGc - 1.0F;
1108  fQvGQa = 3.0F * ftmp * ftmp;
1109  if (fQvGQa < FQVG_9DOF_GBY_KALMAN)
1110  fQvGQa = FQVG_9DOF_GBY_KALMAN;
1111 
1112  // calculate magnetic noise variance relative to geomagnetic sphere
1113  ftmp = fmodBc - pthisMagCal->fB;
1114  fQvBQd = 3.0F * ftmp * ftmp;
1115  if (fQvBQd < FQVB_9DOF_GBY_KALMAN)
1116  fQvBQd = FQVB_9DOF_GBY_KALMAN;
1117 
1118  // do a once-only orientation lock immediately after the first valid magnetic calibration by:
1119  // i) setting the a priori and a posteriori orientations to the 6DOF eCompass orientation
1120  // ii) setting the geomagnetic inclination angle fDeltaPl now that the first calibrated 6DOF estimate is available
1121  if (pthisMagCal->iValidMagCal && !pthisSV->iFirstAccelMagLock) {
1122  fqMi = pthisSV->fqPl = fq6DOF;
1123  f3x3matrixAeqB(fRMi, fR6DOF);
1124  pthisSV->fDeltaPl = fDelta6DOF;
1125  pthisSV->fsinDeltaPl = fsinDelta6DOF;
1126  pthisSV->fcosDeltaPl = fcosDelta6DOF;
1127  pthisSV->iFirstAccelMagLock = true;
1128  }
1129 
1130  // set ftmpA3x1 to the normalized 6DOF gravity vector and set fgMi to the normalized a priori gravity vector
1131  // with both estimates computed in the sensor frame
1132 #if THISCOORDSYSTEM == NED
1133  ftmpA3x1[CHX] = fR6DOF[CHX][CHZ];
1134  ftmpA3x1[CHY] = fR6DOF[CHY][CHZ];
1135  ftmpA3x1[CHZ] = fR6DOF[CHZ][CHZ];
1136  fgMi[CHX] = fRMi[CHX][CHZ];
1137  fgMi[CHY] = fRMi[CHY][CHZ];
1138  fgMi[CHZ] = fRMi[CHZ][CHZ];
1139 #else // ANDROID and WIN8 (ENU gravity positive)
1140  ftmpA3x1[CHX] = -fR6DOF[CHX][CHZ];
1141  ftmpA3x1[CHY] = -fR6DOF[CHY][CHZ];
1142  ftmpA3x1[CHZ] = -fR6DOF[CHZ][CHZ];
1143  fgMi[CHX] = -fRMi[CHX][CHZ];
1144  fgMi[CHY] = -fRMi[CHY][CHZ];
1145  fgMi[CHZ] = -fRMi[CHZ][CHZ];
1146 #endif
1147 
1148  // set ftmpq to the quaternion that rotates the 6DOF gravity tilt vector ftmpA3x1 to the a priori estimate fgMi
1149  // and copy its vector components into the measurement error vector fZErr[0-2].
1150  fveqconjgquq(&ftmpq, ftmpA3x1, fgMi);
1151  pthisSV->fZErr[0] = ftmpq.q1;
1152  pthisSV->fZErr[1] = ftmpq.q2;
1153  pthisSV->fZErr[2] = ftmpq.q3;
1154 
1155  // set ftmpA3x1 to the normalized 6DOF geomagnetic vector and set fmMi to the normalized a priori geomagnetic vector
1156  // with both estimates computed in the sensor frame
1157 #if THISCOORDSYSTEM == NED
1158  ftmpA3x1[CHX] = fR6DOF[CHX][CHX] * fcosDelta6DOF + fR6DOF[CHX][CHZ] * fsinDelta6DOF;
1159  ftmpA3x1[CHY] = fR6DOF[CHY][CHX] * fcosDelta6DOF + fR6DOF[CHY][CHZ] * fsinDelta6DOF;
1160  ftmpA3x1[CHZ] = fR6DOF[CHZ][CHX] * fcosDelta6DOF + fR6DOF[CHZ][CHZ] * fsinDelta6DOF;
1161  fmMi[CHX] = fRMi[CHX][CHX] * pthisSV->fcosDeltaPl + fRMi[CHX][CHZ] * pthisSV->fsinDeltaPl;
1162  fmMi[CHY] = fRMi[CHY][CHX] * pthisSV->fcosDeltaPl + fRMi[CHY][CHZ] * pthisSV->fsinDeltaPl;
1163  fmMi[CHZ] = fRMi[CHZ][CHX] * pthisSV->fcosDeltaPl + fRMi[CHZ][CHZ] * pthisSV->fsinDeltaPl;
1164 #else // ANDROID and WIN8 (both ENU coordinate systems)
1165  ftmpA3x1[CHX] = fR6DOF[CHX][CHY] * fcosDelta6DOF - fR6DOF[CHX][CHZ] * fsinDelta6DOF;
1166  ftmpA3x1[CHY] = fR6DOF[CHY][CHY] * fcosDelta6DOF - fR6DOF[CHY][CHZ] * fsinDelta6DOF;
1167  ftmpA3x1[CHZ] = fR6DOF[CHZ][CHY] * fcosDelta6DOF - fR6DOF[CHZ][CHZ] * fsinDelta6DOF;
1168  fmMi[CHX] = fRMi[CHX][CHY] * pthisSV->fcosDeltaPl - fRMi[CHX][CHZ] * pthisSV->fsinDeltaPl;
1169  fmMi[CHY] = fRMi[CHY][CHY] * pthisSV->fcosDeltaPl - fRMi[CHY][CHZ] * pthisSV->fsinDeltaPl;
1170  fmMi[CHZ] = fRMi[CHZ][CHY] * pthisSV->fcosDeltaPl - fRMi[CHZ][CHZ] * pthisSV->fsinDeltaPl;
1171 #endif
1172 
1173  // set ftmpq to the quaternion that rotates the 6DOF geomagnetic tilt vector ftmpA3x1 to the a priori estimate fmMi
1174  // and copy its vector components into the measurement error vector fZErr[3-5].
1175  fveqconjgquq(&ftmpq, ftmpA3x1, fmMi);
1176  pthisSV->fZErr[3] = ftmpq.q1;
1177  pthisSV->fZErr[4] = ftmpq.q2;
1178  pthisSV->fZErr[5] = ftmpq.q3;
1179 
1180  // update Qw using the a posteriori error vectors from the previous iteration.
1181  // as Qv increases or Qw decreases, K -> 0 and the Kalman filter is weighted towards the a priori prediction
1182  // as Qv decreases or Qw increases, KC -> I and the Kalman filter is weighted towards the measurement.
1183  // initialize on and above diagonal elements of Qw to zero
1184  for (i = 0; i < 9; i++)
1185  for (j = i; j < 9; j++)
1186  pthisSV->fQw9x9[i][j] = 0.0F;
1187  // partial diagonal gyro offset terms
1188  pthisSV->fQw9x9[6][6] = pthisSV->fbErrPl[CHX] * pthisSV->fbErrPl[CHX];
1189  pthisSV->fQw9x9[7][7] = pthisSV->fbErrPl[CHY] * pthisSV->fbErrPl[CHY];
1190  pthisSV->fQw9x9[8][8] = pthisSV->fbErrPl[CHZ] * pthisSV->fbErrPl[CHZ];
1191  // set ftmpA3x1 to alpha^2 / 4 * fbErrPl.fbErrPl + fAlphaSqQvYQwbOver12
1192  ftmpA3x1[0] = pthisSV->fAlphaSqOver4 * pthisSV->fQw9x9[6][6] + pthisSV->fAlphaSqQvYQwbOver12;
1193  ftmpA3x1[1] = pthisSV->fAlphaSqOver4 * pthisSV->fQw9x9[7][7] + pthisSV->fAlphaSqQvYQwbOver12;
1194  ftmpA3x1[2] = pthisSV->fAlphaSqOver4 * pthisSV->fQw9x9[8][8] + pthisSV->fAlphaSqQvYQwbOver12;
1195  // diagonal gravity vector components
1196  pthisSV->fQw9x9[0][0] = pthisSV->fqgErrPl[CHX] * pthisSV->fqgErrPl[CHX] + ftmpA3x1[0];
1197  pthisSV->fQw9x9[1][1] = pthisSV->fqgErrPl[CHY] * pthisSV->fqgErrPl[CHY] + ftmpA3x1[1];
1198  pthisSV->fQw9x9[2][2] = pthisSV->fqgErrPl[CHZ] * pthisSV->fqgErrPl[CHZ] + ftmpA3x1[2];
1199  // diagonal geomagnetic vector components
1200  pthisSV->fQw9x9[3][3] = pthisSV->fqmErrPl[CHX] * pthisSV->fqmErrPl[CHX] + ftmpA3x1[0];
1201  pthisSV->fQw9x9[4][4] = pthisSV->fqmErrPl[CHY] * pthisSV->fqmErrPl[CHY] + ftmpA3x1[1];
1202  pthisSV->fQw9x9[5][5] = pthisSV->fqmErrPl[CHZ] * pthisSV->fqmErrPl[CHZ] + ftmpA3x1[2];
1203  // diagonal gyro offset components
1204  pthisSV->fQw9x9[6][6] += pthisSV->fQwbOver3;
1205  pthisSV->fQw9x9[7][7] += pthisSV->fQwbOver3;;
1206  pthisSV->fQw9x9[8][8] += pthisSV->fQwbOver3;;
1207  // set ftmpA3x1 to alpha / 2 * fQw9x9[6-8][6-8]
1208  ftmpA3x1[0] = pthisSV->fAlphaOver2 * pthisSV->fQw9x9[6][6];
1209  ftmpA3x1[1] = pthisSV->fAlphaOver2 * pthisSV->fQw9x9[7][7];
1210  ftmpA3x1[2] = pthisSV->fAlphaOver2 * pthisSV->fQw9x9[8][8];
1211  // off diagonal gravity and gyro offset components
1212  pthisSV->fQw9x9[0][6] = pthisSV->fqgErrPl[CHX] * pthisSV->fbErrPl[CHX] - ftmpA3x1[0];
1213  pthisSV->fQw9x9[1][7] = pthisSV->fqgErrPl[CHY] * pthisSV->fbErrPl[CHY] - ftmpA3x1[1];
1214  pthisSV->fQw9x9[2][8] = pthisSV->fqgErrPl[CHZ] * pthisSV->fbErrPl[CHZ] - ftmpA3x1[2];
1215  // off diagonal geomagnetic and gyro offset components
1216  pthisSV->fQw9x9[3][6] = pthisSV->fqmErrPl[CHX] * pthisSV->fbErrPl[CHX] - ftmpA3x1[0];
1217  pthisSV->fQw9x9[4][7] = pthisSV->fqmErrPl[CHY] * pthisSV->fbErrPl[CHY] - ftmpA3x1[1];
1218  pthisSV->fQw9x9[5][8] = pthisSV->fqmErrPl[CHZ] * pthisSV->fbErrPl[CHZ] - ftmpA3x1[2];
1219  // set below diagonal elements of Qw to above diagonal elements
1220  for (i = 1; i < 9; i++)
1221  for (j = 0; j < i; j++)
1222  pthisSV->fQw9x9[i][j] = pthisSV->fQw9x9[j][i];
1223 
1224  // calculate the vector fQv6x1 containing the diagonal elements of the measurement covariance matrix Qv
1225  pthisSV->fQv6x1[0] = pthisSV->fQv6x1[1] = pthisSV->fQv6x1[2] = ONEOVER12 * fQvGQa + pthisSV->fAlphaSqQvYQwbOver12;
1226  pthisSV->fQv6x1[3] = pthisSV->fQv6x1[4] = pthisSV->fQv6x1[5] = ONEOVER12 * fQvBQd / pthisMagCal->fBSq + pthisSV->fAlphaSqQvYQwbOver12;
1227 
1228  // calculate the Kalman gain matrix K = Qw * C^T * inv(C * Qw * C^T + Qv)
1229  // set fQwCT9x6 = Qw.C^T where Qw has size 9x9 and C^T has size 9x6
1230  for (i = 0; i < 9; i++) { // loop over rows
1231  for (j = 0; j < 6; j++) { // loop over columns
1232  pthisSV->fQwCT9x6[i][j] = 0.0F;
1233  // accumulate matrix sum
1234  for (k = 0; k < 9; k++) {
1235  // determine fC6x9[j][k] since the matrix is highly sparse
1236  fC6x9jk = 0.0F;
1237  // handle rows 0 to 2
1238  if (j < 3) {
1239  if (k == j) fC6x9jk = 1.0F;
1240  if (k == (j + 6)) fC6x9jk = -pthisSV->fAlphaOver2;
1241  } else if (j < 6) {
1242  // handle rows 3 to 5
1243  if (k == j) fC6x9jk = 1.0F;
1244  if (k == (j + 3)) fC6x9jk = -pthisSV->fAlphaOver2;
1245  }
1246 
1247  // accumulate fQwCT9x6[i][j] += Qw9x9[i][k] * C[j][k]
1248  if ((pthisSV->fQw9x9[i][k] != 0.0F) && (fC6x9jk != 0.0F)) {
1249  if (fC6x9jk == 1.0F) pthisSV->fQwCT9x6[i][j] += pthisSV->fQw9x9[i][k];
1250  else pthisSV->fQwCT9x6[i][j] += pthisSV->fQw9x9[i][k] * fC6x9jk;
1251  }
1252  }
1253  }
1254  }
1255 
1256  // set symmetric ftmpA6x6 = C.(Qw.C^T) + Qv = C.fQwCT9x6 + Qv
1257  for (i = 0; i < 6; i++) { // loop over rows
1258  for (j = i; j < 6; j++) { // loop over on and above diagonal columns
1259  // zero off diagonal and set diagonal to Qv
1260  if (i == j) ftmpA6x6[i][j] = pthisSV->fQv6x1[i];
1261  else ftmpA6x6[i][j] = 0.0F;
1262  // accumulate matrix sum
1263  for (k = 0; k < 9; k++) {
1264  // determine fC6x9[i][k]
1265  fC6x9ik = 0.0F;
1266  // handle rows 0 to 2
1267  if (i < 3) {
1268  if (k == i) fC6x9ik = 1.0F;
1269  if (k == (i + 6)) fC6x9ik = -pthisSV->fAlphaOver2;
1270  } else if (i < 6) {
1271  // handle rows 3 to 5
1272  if (k == i) fC6x9ik = 1.0F;
1273  if (k == (i + 3)) fC6x9ik = -pthisSV->fAlphaOver2;
1274  }
1275 
1276  // accumulate ftmpA6x6[i][j] += C[i][k] & fQwCT9x6[k][j]
1277  if ((fC6x9ik != 0.0F) && (pthisSV->fQwCT9x6[k][j] != 0.0F)) {
1278  if (fC6x9ik == 1.0F) ftmpA6x6[i][j] += pthisSV->fQwCT9x6[k][j];
1279  else ftmpA6x6[i][j] += fC6x9ik * pthisSV->fQwCT9x6[k][j];
1280  }
1281  }
1282  }
1283  }
1284  // set ftmpA6x6 below diagonal elements to above diagonal elements
1285  for (i = 1; i < 6; i++) // loop over rows
1286  for (j = 0; j < i; j++) // loop over below diagonal columns
1287  ftmpA6x6[i][j] = ftmpA6x6[j][i];
1288 
1289  // invert ftmpA6x6 in situ to give ftmpA6x6 = inv(C * Qw * C^T + Qv) = inv(ftmpA6x6)
1290  for (i = 0; i < 6; i++)
1291  pfRows[i] = ftmpA6x6[i];
1292  fmatrixAeqInvA(pfRows, iColInd, iRowInd, iPivot, 6, &ierror);
1293 
1294  // on successful inversion set Kalman gain matrix K9x6 = Qw * C^T * inv(C * Qw * C^T + Qv) = fQwCT9x6 * ftmpA6x6
1295  if (!ierror) {
1296  // normal case
1297  for (i = 0; i < 9; i++) // loop over rows
1298  for (j = 0; j < 6; j++) { // loop over columns
1299  pthisSV->fK9x6[i][j] = 0.0F;
1300  for (k = 0; k < 6; k++) {
1301  if ((pthisSV->fQwCT9x6[i][k] != 0.0F) && (ftmpA6x6[k][j] != 0.0F))
1302  pthisSV->fK9x6[i][j] += pthisSV->fQwCT9x6[i][k] * ftmpA6x6[k][j];
1303  }
1304  }
1305  } else {
1306  // ftmpA6x6 was singular so set Kalman gain matrix to zero
1307  for (i = 0; i < 9; i++) // loop over rows
1308  for (j = 0; j < 6; j++) // loop over columns
1309  pthisSV->fK9x6[i][j] = 0.0F;
1310  }
1311 
1312  // calculate the a posteriori gravity and geomagnetic tilt quaternion errors and gyro offset error vector
1313  // from the Kalman matrix fK9x6 and the measurement error vector fZErr.
1314  for (i = CHX; i <= CHZ; i++) {
1315  pthisSV->fqgErrPl[i] = pthisSV->fqmErrPl[i] = pthisSV->fbErrPl[i] = 0.0F;
1316  for (j = 0; j < 6; j++) {
1317  // calculate gravity tilt quaternion vector error component fqgErrPl
1318  if (pthisSV->fK9x6[i][j] != 0.0F)
1319  pthisSV->fqgErrPl[i] += pthisSV->fK9x6[i][j] * pthisSV->fZErr[j];
1320 
1321  // calculate geomagnetic tilt quaternion vector error component fqmErrPl
1322  if (pthisSV->fK9x6[i + 3][j] != 0.0F)
1323  pthisSV->fqmErrPl[i] += pthisSV->fK9x6[i + 3][j] * pthisSV->fZErr[j];
1324 
1325  // calculate gyro offset vector error component fbErrPl
1326  if (pthisSV->fK9x6[i + 6][j] != 0.0F)
1327  pthisSV->fbErrPl[i] += pthisSV->fK9x6[i + 6][j] * pthisSV->fZErr[j];
1328  }
1329  }
1330 
1331  // set ftmpq to the a posteriori gravity tilt correction (conjugate) quaternion
1332  ftmpq.q1 = -pthisSV->fqgErrPl[CHX];
1333  ftmpq.q2 = -pthisSV->fqgErrPl[CHY];
1334  ftmpq.q3 = -pthisSV->fqgErrPl[CHZ];
1335  ftmpq.q0 = sqrtf(fabs(1.0F - ftmpq.q1 * ftmpq.q1 - ftmpq.q2 * ftmpq.q2 - ftmpq.q3 * ftmpq.q3));
1336 
1337  // set ftmpA3x3 to the gravity tilt correction matrix and rotate the normalized a priori estimate of the
1338  // gravity vector fgMi to obtain the normalized a posteriori estimate of the gravity vector fgPl
1339  fRotationMatrixFromQuaternion(ftmpA3x3, &ftmpq);
1340  fveqRu(fgPl, ftmpA3x3, fgMi, 0);
1341 
1342  // set ftmpq to the a posteriori geomagnetic tilt correction (conjugate) quaternion
1343  ftmpq.q1 = -pthisSV->fqmErrPl[CHX];
1344  ftmpq.q2 = -pthisSV->fqmErrPl[CHY];
1345  ftmpq.q3 = -pthisSV->fqmErrPl[CHZ];
1346  ftmpq.q0 = sqrtf(fabs(1.0F - ftmpq.q1 * ftmpq.q1 - ftmpq.q2 * ftmpq.q2 - ftmpq.q3 * ftmpq.q3));
1347 
1348  // set ftmpA3x3 to the geomagnetic tilt correction matrix and rotate the normalized a priori estimate of the
1349  // geomagnetic vector fmMi to obtain the normalized a posteriori estimate of the geomagnetic vector fmPl
1350  fRotationMatrixFromQuaternion(ftmpA3x3, &ftmpq);
1351  fveqRu(fmPl, ftmpA3x3, fmMi, 0);
1352 
1353  // compute the a posteriori orientation matrix fRPl from the vector product of the a posteriori gravity fgPl
1354  // and geomagnetic fmPl vectors both of which are normalized
1355 #if THISCOORDSYSTEM == NED// gravity vector is +z and accel measurement is +z when flat so don't negate
1356  feCompassNED(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
1357  fmPl, fgPl, &fmodBc, &fmodGc);
1358 #elif THISCOORDSYSTEM == ANDROID // gravity vector is -z and accel measurement is +z when flat so negate
1359  ftmpA3x1[CHX] = -fgPl[CHX];
1360  ftmpA3x1[CHY] = -fgPl[CHY];
1361  ftmpA3x1[CHZ] = -fgPl[CHZ];
1362  feCompassAndroid(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
1363  fmPl, ftmpA3x1, &fmodBc, &fmodGc);
1364 #else // WIN8// gravity vector is -z and accel measurement is -1g when flat so don't negate
1365  feCompassWin8(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
1366  fmPl, fgPl, &fmodBc, &fmodGc);
1367 #endif
1368 
1369  // compute the a posteriori quaternion fqPl and rotation vector fRVecPl from fRPl
1370  fQuaternionFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fqPl));
1371  fRotationVectorDegFromQuaternion(&(pthisSV->fqPl), pthisSV->fRVecPl);
1372 
1373  // update the a posteriori gyro offset vector: b+[k] = b-[k] - be+[k] = b+[k] - be+[k] (deg/s)
1374  for (i = CHX; i <= CHZ; i++) {
1375  // restrict the gyro offset correction to the maximum permitted by the random walk model
1376  if (pthisSV->fbErrPl[i] > pthisSV->fMaxGyroOffsetChange)
1377  pthisSV->fbPl[i] -= pthisSV->fMaxGyroOffsetChange;
1378  else if (pthisSV->fbErrPl[i] < -pthisSV->fMaxGyroOffsetChange)
1379  pthisSV->fbPl[i] += pthisSV->fMaxGyroOffsetChange;
1380  else
1381  pthisSV->fbPl[i] -= pthisSV->fbErrPl[i];
1382  // restrict gyro offset between specified limits
1383  if (pthisSV->fbPl[i] > FMAX_9DOF_GBY_BPL) pthisSV->fbPl[i] = FMAX_9DOF_GBY_BPL;
1384  if (pthisSV->fbPl[i] < FMIN_9DOF_GBY_BPL) pthisSV->fbPl[i] = FMIN_9DOF_GBY_BPL;
1385  }
1386 
1387  // compute the linear acceleration fAccGl in the global frame
1388  // first de-rotate the accelerometer measurement fGc from the sensor to global frame
1389  // using the transpose (inverse) of the orientation matrix fRPl
1390  fveqRu(pthisSV->fAccGl, pthisSV->fRPl, pthisAccel->fGc, 1);
1391 
1392  // subtract the fixed gravity vector in the global frame leaving linear acceleration
1393 #if THISCOORDSYSTEM == NED
1394  // gravity positive NED
1395  pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
1396  pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
1397  pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] - 1.0F);
1398 #elif THISCOORDSYSTEM == ANDROID
1399  // acceleration positive ENU
1400  pthisSV->fAccGl[CHZ] = pthisSV->fAccGl[CHZ] - 1.0F;
1401 #else // WIN8
1402  // gravity positive ENU
1403  pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
1404  pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
1405  pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] + 1.0F);
1406 #endif
1407 
1408  // integrate the acceleration to velocity and displacement in the global frame.
1409  // Note: integration errors accumulate without limit over time and this code should only be
1410  // used for inertial integration of the order of seconds.
1411  for (i = CHX; i <= CHZ; i++) {
1412  // integrate acceleration (in g) to velocity in m/s
1413  pthisSV->fVelGl[i] += pthisSV->fAccGl[i] * pthisSV->fgdeltat;
1414  // integrate velocity (in m/s) to displacement (m)
1415  pthisSV->fDisGl[i] += pthisSV->fVelGl[i] * pthisSV->fdeltat;
1416  }
1417 
1418  // compute the a posteriori Euler angles from the a posteriori orientation matrix fRPl
1419 #if THISCOORDSYSTEM == NED
1420  fNEDAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl), &(pthisSV->fThePl), &(pthisSV->fPsiPl), &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1421 #elif THISCOORDSYSTEM == ANDROID
1422  fAndroidAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl), &(pthisSV->fThePl), &(pthisSV->fPsiPl), &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1423 #else // WIN8
1424  fWin8AnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl), &(pthisSV->fThePl), &(pthisSV->fPsiPl), &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1425 #endif
1426 
1427  return;
1428 } // end fRun_9DOF_GBY_KALMAN
1429 #endif // #if F_9DOF_GBY_KALMAN
float fLPThe
low pass pitch (deg)
float flpf
low pass filter coefficient
Quaternion fqPl
a posteriori orientation quaternion
int32_t systick
systick timer
float fLPR[3][3]
low pass filtered orientation matrix
float fOmega[3]
average angular velocity (deg/s)
void fRotationVectorDegFromQuaternion(Quaternion *pq, float rvecdeg[])
computes rotation vector (deg) from rotation quaternion
Definition: orientation.c:846
#define FLPFSECS_3DOF_G_BASIC
tilt orientation low pass filter time constant (s)
Definition: fusion.h:31
The PressureSensor structure stores raw and processed measurements for an altimeter.
float fbPl[3]
gyro offset (deg/s)
float fcosDeltaPl
cos(fDeltaPl)
float fAlphaQwbOver6
(PI / 180 * fdeltat) * Qwb / 6
void f3DOFMagnetometerMatrixAndroid(float fR[][3], float fBc[])
Android magnetometer 3DOF flat eCompass function, computing rotation matrix fR.
Definition: orientation.c:229
float q3
z vector component
Definition: orientation.h:25
void f3DOFTiltAndroid(float fR[][3], float fGc[])
Android accelerometer 3DOF tilt function computing, rotation matrix fR.
float fK9x6[9][6]
kalman filter gain matrix K
float fQwbOver3
Qwb / 3.
float q0
scalar component
Definition: orientation.h:22
#define FMIN_9DOF_GBY_BPL
minimum permissible power on gyro offsets (deg/s)
Definition: fusion.h:61
float fRPl[3][3]
a posteriori orientation matrix
void fInit_3DOF_B_BASIC(struct SV_3DOF_B_BASIC *pthisSV, struct MagSensor *pthisMag, float flpftimesecs)
Definition: fusion.c:207
float fMaxGyroOffsetChange
maximum permissible gyro offset change per iteration (deg/s)
float fAlphaSqQvYQwbOver12
(PI / 180 * fdeltat)^2 * (QvY + Qwb) / 12
float fbPl[3]
gyro offset (deg/s)
void fLPFOrientationQuaternion(Quaternion *pq, Quaternion *pLPq, float flpf, float fdeltat, float fOmega[])
function low pass filters an orientation quaternion and computes virtual gyro rotation rate ...
Definition: orientation.c:896
float fqgErrPl[3]
gravity vector tilt orientation quaternion error (dimensionless)
The SV_1DOF_P_BASIC structure contains state information for a pressure sensor/altimeter.
float fPsiPl
yaw (deg)
The MagSensor structure stores raw and processed measurements for a 3-axis magnetic sensor...
void f3DOFMagnetometerMatrixNED(float fR[][3], float fBc[])
Aerospace NED magnetometer 3DOF flat eCompass function, computing rotation matrix fR...
Definition: orientation.c:199
#define GYRO_NVM_OFFSET
Definition: frdm_k64f.h:154
void f3x3matrixAeqI(float A[][3])
function sets the 3x3 matrix A to the identity matrix
Definition: matrix.c:27
float fAccGl[3]
linear acceleration (g) in global frame
float fQw6x6[6][6]
covariance matrix Qw
float fDeltaPl
a posteriori inclination angle from Kalman filter (deg)
void feCompassNED(float fR[][3], float *pfDelta, float *pfsinDelta, float *pfcosDelta, float fBc[], float fGc[], float *pfmodBc, float *pfmodGc)
NED: basic 6DOF e-Compass function, computing rotation matrix fR and magnetic inclination angle fDelt...
Definition: orientation.c:270
float fRPl[3][3]
a posteriori orientation matrix
float fBSq
square of fB (uT^2)
Definition: magnetic.h:61
float fGc[3]
averaged precision calibrated measurement (g)
uint32_t uint32
Definition: sensor_fusion.h:44
int32_t iValidMagCal
solver used: 0 (no calibration) or 4, 7, 10 element
Definition: magnetic.h:63
float fRhoPl
compass (deg)
SV_6DOF_GY_KALMAN is the 6DOF Kalman filter accelerometer and gyroscope state vector structure...
int32 ARM_systick_elapsed_ticks(int32 start_ticks)
#define FQWB_9DOF_GBY_KALMAN
gyro offset random walk units (deg/s)^2
Definition: fusion.h:60
float q1
x vector component
Definition: orientation.h:23
float fRVecPl[3]
rotation vector
void feCompassWin8(float fR[][3], float *pfDelta, float *pfsinDelta, float *pfcosDelta, float fBc[], float fGc[], float *pfmodBc, float *pfmodGc)
Win8: basic 6DOF e-Compass function, computing rotation matrix fR and magnetic inclination angle fDel...
Definition: orientation.c:417
This is the 3DOF basic magnetometer state vector structure/.
float fAlphaSqOver4
(PI / 180 * fdeltat)^2 / 4
void fveqRu(float fv[], float fR[][3], float fu[], int8 itranspose)
Definition: matrix.c:802
void fInitializeFusion(SensorFusionGlobals *sfg)
Definition: fusion.c:33
#define FUSION_HZ
(int) actual rate of fusion algorithm execution and sensor FIFO reads
void fRun_3DOF_Y_BASIC(struct SV_3DOF_Y_BASIC *pthisSV, struct GyroSensor *pthisGyro)
Definition: fusion.c:534
#define FQWB_6DOF_GY_KALMAN
gyro offset random walk units (deg/s)^2
Definition: fusion.h:48
void fInit_6DOF_GY_KALMAN(struct SV_6DOF_GY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct GyroSensor *pthisGyro)
Definition: fusion.c:282
int32_t systick
systick timer;
void fRotationMatrixFromQuaternion(float R[][3], const Quaternion *pq)
compute the rotation matrix from an orientation quaternion
Definition: orientation.c:806
#define CHX
Used to access X-channel entries in various data data structures.
Definition: sensor_fusion.h:60
int8_t resetflag
flag to request re-initialization on next pass
int8_t resetflag
flag to request re-initialization on next pass
float fLPRVec[3]
rotation vector
float fLPDelta
low pass filtered inclination angle (deg)
float fMaxGyroOffsetChange
maximum permissible gyro offset change per iteration (deg/s)
Defines control sub-system.
void f3x3matrixAeqB(float A[][3], float B[][3])
function sets 3x3 matrix A to 3x3 matrix B
Definition: matrix.c:48
void fFuseSensors(struct SV_1DOF_P_BASIC *pthisSV_1DOF_P_BASIC, struct SV_3DOF_G_BASIC *pthisSV_3DOF_G_BASIC, struct SV_3DOF_B_BASIC *pthisSV_3DOF_B_BASIC, struct SV_3DOF_Y_BASIC *pthisSV_3DOF_Y_BASIC, struct SV_6DOF_GB_BASIC *pthisSV_6DOF_GB_BASIC, struct SV_6DOF_GY_KALMAN *pthisSV_6DOF_GY_KALMAN, struct SV_9DOF_GBY_KALMAN *pthisSV_9DOF_GBY_KALMAN, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag, struct GyroSensor *pthisGyro, struct PressureSensor *pthisPressure, struct MagCalibration *pthisMagCal)
Definition: fusion.c:68
float fPsiPl
yaw (deg)
void fQuaternionFromRotationMatrix(float R[][3], Quaternion *pq)
compute the orientation quaternion from a 3x3 rotation matrix
Definition: orientation.c:765
void fqAeq1(Quaternion *pqA)
set a quaternion to the unit quaternion
Definition: orientation.c:1018
float fQwCT9x6[9][6]
Qw.C^T matrix.
#define FQVY_6DOF_GY_KALMAN
gyro sensor noise variance units (deg/s)^2
Definition: fusion.h:46
float fQv6x1[6]
measurement noise covariance matrix leading diagonal
float fAlphaOver2
PI / 180 * fdeltat / 2.
Provides function prototypes for driver level interfaces.
float fThePl
pitch (deg)
float fPhi
roll (deg)
float fPhiPl
roll (deg)
float fLPRho
low pass compass (deg)
void fNEDAnglesDegFromRotationMatrix(float R[][3], float *pfPhiDeg, float *pfTheDeg, float *pfPsiDeg, float *pfRhoDeg, float *pfChiDeg)
extract the NED angles in degrees from the NED rotation matrix
Definition: orientation.c:492
Quaternion fLPq
low pass filtered orientation quaternion
SV_6DOF_GB_BASIC is the 6DOF basic accelerometer and magnetometer state vector structure.
int32_t loopcounter
counter incrementing each iteration of sensor fusion (typically 25Hz)
float fDegPerSecPerCount
deg/s per count
int16_t iYsFIFO[GYRO_FIFO_SIZE][3]
FIFO measurements (counts)
int8_t resetflag
flag to request re-initialization on next pass
int32_t systick
systick timer
float fLPRVec[3]
rotation vector
void fRun_1DOF_P_BASIC(struct SV_1DOF_P_BASIC *pthisSV, struct PressureSensor *pthisPressure)
Definition: fusion.c:419
float fLPRVec[3]
rotation vector
void feCompassAndroid(float fR[][3], float *pfDelta, float *pfsinDelta, float *pfcosDelta, float fBc[], float fGc[], float *pfmodBc, float *pfmodGc)
Android: basic 6DOF e-Compass function, computing rotation matrix fR and magnetic inclination angle f...
Definition: orientation.c:343
Functions to convert between various orientation representations.
void qAeqAxB(Quaternion *pqA, const Quaternion *pqB)
function compute the quaternion product qA = qA * qB
Definition: orientation.c:953
float fR[3][3]
unfiltered orientation matrix
void f3DOFTiltNED(float fR[][3], float fGc[])
Aerospace NED accelerometer 3DOF tilt function, computing rotation matrix fR.
#define FPIOVER180
degrees to radians conversion = pi / 180
Definition: sensor_fusion.h:83
void qAeqBxC(Quaternion *pqA, const Quaternion *pqB, const Quaternion *pqC)
function compute the quaternion product qB * qC
Definition: orientation.c:942
float fRVec[3]
rotation vector
float fLPRho
low pass compass (deg)
Quaternion fLPq
low pass filtered orientation quaternion
float fVelGl[3]
velocity (m/s) in global frame
float fZErr[3]
measurement error vector
Lower level sensor fusion interface.
int8_t resetflag
flag to request re-initialization on next pass
#define FMIN_6DOF_GY_BPL
minimum permissible power on gyro offsets (deg/s)
Definition: fusion.h:49
float fR[3][3]
unfiltered orientation matrix
#define FQVG_9DOF_GBY_KALMAN
accelerometer sensor noise variance units g^2 defining minimum deviation from 1g sphere ...
Definition: fusion.h:58
#define FMAX_6DOF_GY_BPL
maximum permissible power on gyro offsets (deg/s)
Definition: fusion.h:50
float fRVecPl[3]
rotation vector
float fBc[3]
averaged calibrated measurement (uT)
float fAlphaQwbOver6
(PI / 180 * fdeltat) * Qwb / 6
float fT
most recent unaveraged temperature (C)
float fLPChi
low pass tilt from vertical (deg)
Quaternion fLPq
low pass filtered orientation quaternion
void fInit_9DOF_GBY_KALMAN(struct SV_9DOF_GBY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag, struct GyroSensor *pthisGyro, struct MagCalibration *pthisMagCal)
Definition: fusion.c:346
#define CHY
Used to access Y-channel entries in various data data structures.
Definition: sensor_fusion.h:61
float q2
y vector component
Definition: orientation.h:24
float fLPT
low pass filtered temperature (C)
uint8_t iFIFOCount
number of measurements read from FIFO
float fOmega[3]
angular velocity (deg/s)
void fInit_1DOF_P_BASIC(struct SV_1DOF_P_BASIC *pthisSV, struct PressureSensor *pthisPressure, float flpftimesecs)
Definition: fusion.c:154
void fRun_6DOF_GY_KALMAN(struct SV_6DOF_GY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct GyroSensor *pthisGyro)
Definition: fusion.c:635
void fInit_6DOF_GB_BASIC(struct SV_6DOF_GB_BASIC *pthisSV, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag, float flpftimesecs)
Definition: fusion.c:250
void fInit_3DOF_G_BASIC(struct SV_3DOF_G_BASIC *pthisSV, struct AccelSensor *pthisAccel, float flpftimesecs)
Definition: fusion.c:179
float fChi
tilt from vertical (deg)
The top level fusion structure.
SensorFusionGlobals sfg
int8_t resetflag
flag to request re-initialization on next pass
#define FQVB_9DOF_GBY_KALMAN
magnetometer sensor noise variance units uT^2 defining minimum deviation from geomagnetic sphere...
Definition: fusion.h:59
#define FLPFSECS_3DOF_B_BASIC
2D eCompass orientation low pass filter time constant (s)
Definition: fusion.h:36
float fH
most recent unaveraged height (m)
The sensor_fusion.h file implements the top level programming interface.
#define ONEOVER12
1 / 12
Definition: sensor_fusion.h:89
float fAccGl[3]
linear acceleration (g) in global frame
void f3DOFMagnetometerMatrixWin8(float fR[][3], float fBc[])
Windows 8 magnetometer 3DOF flat eCompass function, computing rotation matrix fR. ...
Definition: orientation.c:259
float fQwCT6x3[6][3]
Qw.C^T matrix.
#define CALIBRATION_NVM_ADDR
start of final 4K (sector size) of 1M flash
Definition: frdm_k64f.h:147
float fdeltat
fusion filter sampling interval (s)
int8_t iFirstAccelMagLock
denotes that 9DOF orientation has locked to 6DOF eCompass
float fLPChi
low pass tilt from vertical (deg)
float fdeltat
fusion time interval (s)
void fqAeqNormqA(Quaternion *pqA)
function normalizes a rotation quaternion and ensures q0 is non-negative
Definition: orientation.c:983
int32_t systick
systick timer
float fdeltat
fusion time interval (s)
float fLPThe
low pass pitch (deg)
#define CHZ
Used to access Z-channel entries in various data data structures.
Definition: sensor_fusion.h:62
float fOmega[3]
virtual gyro angular velocity (deg/s)
float fLPPhi
low pass roll (deg)
Matrix manipulation functions.
float flpf
low pass filter coefficient
quaternion structure definition
Definition: orientation.h:20
void f3DOFTiltWin8(float fR[][3], float fGc[])
Windows 8 accelerometer 3DOF tilt function computing, rotation matrix fR.
float fThe
pitch (deg)
void fRun_3DOF_B_BASIC(struct SV_3DOF_B_BASIC *pthisSV, struct MagSensor *pthisMag)
Definition: fusion.c:489
float fB
current geomagnetic field magnitude (uT)
Definition: magnetic.h:60
void fInit_3DOF_Y_BASIC(struct SV_3DOF_Y_BASIC *pthisSV)
Definition: fusion.c:235
#define FMAX_9DOF_GBY_BPL
maximum permissible power on gyro offsets (deg/s)
Definition: fusion.h:62
float fDisGl[3]
displacement (m) in global frame
void fmatrixAeqInvA(float *A[], int8 iColInd[], int8 iRowInd[], int8 iPivot[], int8 isize, int8 *pierror)
Definition: matrix.c:648
float fThePl
pitch (deg)
#define FQVY_9DOF_GBY_KALMAN
gyro sensor noise variance units (deg/s)^2
Definition: fusion.h:57
void fRun_6DOF_GB_BASIC(struct SV_6DOF_GB_BASIC *pthisSV, struct MagSensor *pthisMag, struct AccelSensor *pthisAccel)
Definition: fusion.c:582
float fgdeltat
g (m/s2) * fdeltat
float fPsi
yaw (deg)
float fR[3][3]
unfiltered orientation matrix
float fLPH
low pass filtered height (m)
float fsinDeltaPl
sin(fDeltaPl)
float fbErrPl[3]
gyro offset error (deg/s)
float fdeltat
fusion time interval (s)
float fQv
measurement noise covariance matrix leading diagonal
float fLPThe
low pass pitch (deg)
float fLPRho
low pass compass (deg)
float fZErr[6]
measurement error vector
#define FQVG_6DOF_GY_KALMAN
accelerometer sensor noise variance units g^2
Definition: fusion.h:47
float fLPPsi
low pass yaw (deg)
float flpf
low pass filter coefficient
Quaternion fq
unfiltered orientation quaternion
float fLPPsi
low pass yaw (deg)
float fLPPhi
low pass roll (deg)
The AccelSensor structure stores raw and processed measurements for a 3-axis accelerometer.
float fOmega[3]
angular velocity (deg/s)
#define FLPFSECS_1DOF_P_BASIC
pressure low pass filter time constant (s)
Definition: fusion.h:26
float fqmErrPl[3]
geomagnetic vector tilt orientation quaternion error (dimensionless)
int8_t resetflag
flag to request re-initialization on next pass
float fYs[3]
averaged measurement (deg/s)
float fAlphaSqQvYQwbOver12
(PI / 180 * fdeltat)^2 * (QvY + Qwb) / 12
float fqgErrPl[3]
gravity vector tilt orientation quaternion error (dimensionless)
float fdeltat
fusion time interval (s)
void fveqconjgquq(Quaternion *pfq, float fu[], float fv[])
Definition: orientation.c:1028
int8_t int8
Definition: sensor_fusion.h:39
float fAlphaSqOver4
(PI / 180 * fdeltat)^2 / 4
int32_t systick
systick timer;
float fdeltat
sensor fusion interval (s)
float fLPChi
low pass tilt from vertical (deg)
float fbErrPl[3]
gyro offset error (deg/s)
Quaternion fq
unfiltered orientation quaternion
float fLPPsi
low pass yaw (deg)
float fLPPhi
low pass roll (deg)
int32_t systick
systick timer
float fOmega[3]
average angular velocity (deg/s)
void fAndroidAnglesDegFromRotationMatrix(float R[][3], float *pfPhiDeg, float *pfTheDeg, float *pfPsiDeg, float *pfRhoDeg, float *pfChiDeg)
extract the Android angles in degrees from the Android rotation matrix
Definition: orientation.c:548
int16_t iYs[3]
average measurement (counts)
void fRun_9DOF_GBY_KALMAN(struct SV_9DOF_GBY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag, struct GyroSensor *pthisGyro, struct MagCalibration *pthisMagCal)
float flpf
low pass filter coefficient
Quaternion fq
unfiltered orientation quaternion
This is the 3DOF basic accelerometer state vector structure.
Quaternion fqPl
a posteriori orientation quaternion
float fLPR[3][3]
low pass filtered orientation matrix
#define GTOMSEC2
standard gravity in m/s2
Definition: sensor_fusion.h:95
float fRhoPl
compass (deg)
int8_t resetflag
flag to request re-initialization on next pass
float fR[3][3]
unfiltered orientation matrix
float fK6x3[6][3]
kalman filter gain matrix K
The GyroSensor structure stores raw and processed measurements for a 3-axis gyroscope.
float fDelta
unfiltered inclination angle (deg)
Math approximations file.
void fRun_3DOF_G_BASIC(struct SV_3DOF_G_BASIC *pthisSV, struct AccelSensor *pthisAccel)
Definition: fusion.c:439
float fChiPl
tilt from vertical (deg)
float fPhiPl
roll (deg)
float fChiPl
tilt from vertical (deg)
float fQw9x9[9][9]
covariance matrix Qw
float fAlphaOver2
PI / 180 * fdeltat / 2.
float fdeltat
sensor fusion interval (s)
void fWin8AnglesDegFromRotationMatrix(float R[][3], float *pfPhiDeg, float *pfTheDeg, float *pfPsiDeg, float *pfRhoDeg, float *pfChiDeg)
extract the Windows 8 angles in degrees from the Windows 8 rotation matrix
Definition: orientation.c:605
Quaternion fq
unfiltered orientation quaternion
void fQuaternionFromRotationVectorDeg(Quaternion *pq, const float rvecdeg[], float fscaling)
computes normalized rotation quaternion from a rotation vector (deg)
Definition: orientation.c:697
int32_t systick
systick timer
float fQwbOver3
Qwb / 3.
float fOmega[3]
angular velocity (deg/s)
float fLPR[3][3]
low pass filtered orientation matrix
float fRho
compass (deg)
SV_9DOF_GBY_KALMAN is the 9DOF Kalman filter accelerometer, magnetometer and gyroscope state vector s...
Magnetic Calibration Structure.
Definition: magnetic.h:55
#define FLPFSECS_6DOF_GB_BASIC
Definition: fusion.h:41
SV_3DOF_Y_BASIC structure is the 3DOF basic gyroscope state vector structure.
void ARM_systick_start_ticks(int32 *pstart)